﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Diagnostics;
using System.Linq;
using System.ServiceProcess;
using System.Text;
using System.IO;
using System.Threading;
using Valentia.Kinection.DataTypes;
using Valentia.Kinection.Manager;
using Valentia.Kinection.Remoting.Helper;
using Valentia.Kinection.Service;
using Valentia.Kinection.Configuration;

namespace Valentia.Kinection.Service
{
    /// <summary>
    /// Kinect proxey service class.
    /// </summary>
    public partial class KinectService : ServiceBase
    {
        #region DATA MEMBERS
        /// <summary>
        /// Kinection service name.
        /// </summary>
        public const String ThisServiceName = "Valentia Kinection Service";

        private bool serviceRunning = false, runSecureSocketServer = true;
        private int remotingServerPort = 15000, remotingServerStatusInterval = 60000;
        private int webSocketPort = 2011, webSocketSecurePort = 2012;
        private String remotingChannelName = "kinectRemotingServer.Rem";
        private String webSocketServerIP = "Any", webSocketSecureServerIP = "Any";
        private String certificatePath = String.Empty;

        private Server remotingServer = null;
        private KinectManager kinectManager = null;
        private WebSocketServerWrapper socketServer;
        private ServiceSetting serviceSetting;
        private System.Timers.Timer remotingServerStatusViewTimer;
        #endregion

        #region CONSTRUCTOR
        /// <summary>
        /// Constructor.
        /// </summary>
        public KinectService()
        {
            InitializeComponent();
            this.ServiceName = KinectService.ThisServiceName;
        }
        #endregion

        #region OVERIDDEN METHODS
        /// <summary>
        /// Called on start of service.
        /// </summary>
        /// <param name="args">Argument array.</param>
        protected override void OnStart(string[] args)
        {
            try
            {
                if (this.serviceRunning) return;

                //Thread.Sleep(10000);

                List<ServiceSetting> serviceSettings = ConfigurationHelper.GetServiceSettings();
                if (serviceSettings == null || serviceSettings.Count <= 0) { throw new Exception("Service settings not found in config database."); }
                
                this.serviceSetting = serviceSettings[0];
                this.remotingServerPort = this.serviceSetting.RemotingServerPort;
                this.remotingChannelName = this.serviceSetting.RemotingChannelName;
                this.remotingServerStatusInterval = 60000;

                this.webSocketServerIP = this.serviceSetting.WebSocketServerIP;
                this.webSocketPort = this.serviceSetting.WebSocketServerPort;
                this.webSocketSecureServerIP = this.serviceSetting.SecureWebSocketServerIP;
                this.webSocketSecurePort = this.serviceSetting.SecureWebSocketServerPort;
                this.runSecureSocketServer = this.serviceSetting.RunSecureSocketServer;
                this.certificatePath = this.serviceSetting.ServerWebScoketCertificate;
                Logger.LogFolder = this.serviceSetting.LogDirectory;
                Logger.LoggingEnabled = this.serviceSetting.IsLoggingEnabled;
                Logger.ApplicationName = KinectService.ThisServiceName;
                
                this.serviceRunning = true;
                this.StartAll();
                base.OnStart(args);

                this.Log("Service started successfully");
            }
            catch (Exception exp)
            {
                { try { this.StopAll(); } catch { } }
                this.Log(exp, "Starting service failed due to exception: {0}", exp.Message);
                this.Stop();
            }
        }

        /// <summary>
        /// Called on stop of service.
        /// </summary>
        protected override void OnStop()
        {
            try
            {
                if (!this.serviceRunning) return;

                this.serviceRunning = false;
                this.StopAll();
                base.OnStop();

                this.Log("Service stopped successfully");
            }
            catch (Exception exp) { this.Log(exp, "Stopping service failed due to exception: {0}", exp.Message); }
        }
        #endregion

        #region EVENT HANDLER METHODS
        private void remotingServerStatusViewTimer_Elapsed(object sender, System.Timers.ElapsedEventArgs e)
        {
            try
            {
                if (!this.serviceRunning) return;

                this.remotingServerStatusViewTimer.Stop();

                if (this.remotingServer.IsServerRunning) return;
                this.remotingServer.Start();
            }
            catch (Exception exp) { this.Log(exp, "Exception occurred while starting remoting server"); }
            finally { if (this.serviceRunning) { this.remotingServerStatusViewTimer.Start(); } }
        }

        private void remotingServer_CommandFromClientReceived(ClientCommands command, String loginName, String password)
        {
            try
            {
                this.Log("Command received from remoting client: {0}", command.ToString());

                switch (command)
                {
                    case ClientCommands.ClientConnected:
                        {
                        }
                        break;
                    case ClientCommands.SendConnectivityStatus:
                        {
                            if (AuthenticateUtilityUser(loginName, password))
                            {
                                int connectedDevicesCount = 0;
                                int connectedClientsCount = 0;

                                this.remotingServer.RaiseConnectionStatusInfoEventOnClient(this.GetConnectionStatus(out connectedDevicesCount
                                    , out connectedClientsCount), connectedDevicesCount, connectedClientsCount, null);
                            }
                            else
                            {
                                this.remotingServer.RaiseConnectionStatusInfoEventOnClient(ConnectivityStatus.BothDisconnected, -1
                                    , -1, "Invalid client credentials");
                            }
                        }
                        break;
                    case ClientCommands.SendClientsConnectivity:
                        {
                            if (AuthenticateUtilityUser(loginName, password))
                            {
                                this.remotingServer.RaiseClientConnectivityInfoEventOnClient(this.socketServer.GetUsers(), null);
                            }
                            else
                            {
                                this.remotingServer.RaiseClientConnectivityInfoEventOnClient(null, "Invalid client credentials");
                            }
                        }
                        break;
                    case ClientCommands.SendDevicesConnectivity:
                        {
                            if (AuthenticateUtilityUser(loginName, password))
                            {
                                this.remotingServer.RaiseDeviceConnectivityInfoEventOnClient(this.kinectManager.GetKinectDevices(), null);
                            }
                            else
                            {
                                this.remotingServer.RaiseDeviceConnectivityInfoEventOnClient(null, "Invalid client credentials");
                            }
                        }
                        break;
                    case ClientCommands.MoveDeviceUp:
                        {
                            if (AuthenticateUtilityUser(loginName, password))
                            {
                                this.kinectManager.MoveKinectUp();
                            }
                        }
                        break;
                    case ClientCommands.MoveDeviceDown:
                        {
                            if (AuthenticateUtilityUser(loginName, password))
                            {
                                this.kinectManager.MoveKinectDown();
                            }
                        }
                        break;
                }
            }
            catch (Exception exp) { this.Log(exp, "exception while command: {0} received from client. {1}", command.ToString(), exp.Message); }
        }

        private void kinectManager_FrameReceived(object sender, FrameReceivedArgs e)
        {
            this.socketServer.OnFrameReceived(e);
        }

        private void kinectManager_SkeletonFrameReceived(object sender, SkeletonFrameReceivedArgs e)
        {
            this.socketServer.OnSkeletonFrameReceived(e);
        }

        private void kinectManager_NoSkeletonFrame(object sender, NoSkeletonFrameArgs e)
        {
            this.socketServer.OnSkeletonFrameReceived(
                new SkeletonFrameReceivedArgs(e.KinectDevice, KinectImageResolution.Resolution640x480, new SkeletonJointCollection[0]));
        }

        private void kinectManager_NotificationReceived(object sender, NotificationReceivedArgs e)
        {
            try
            {
                this.Log(e.Exception, "Notification received: {0}. {1}", e.NotificationType.ToString(), e.Message);

                if (!this.remotingServer.IsServerRunning) return;

                if (this.IsRegisteredNotification(e.NotificationType))
                {
                    this.remotingServer.RaiseNotificationEventOnClient(e);
                }
            }
            catch (Exception exp) { this.Log(exp, "Exception while sending notification to remoting client. {1}", exp.Message); }

        }
        #endregion

        #region PRIVATE METHODS
        private void StartAll()
        {
            this.StartKinect();

            if (this.remotingServer == null)
            {
                this.remotingServer = new Server(remotingServerPort, remotingChannelName);
                this.remotingServer.CommandFromClientReceived 
                    += new Server.CommandFromClientReceivedDlg(remotingServer_CommandFromClientReceived);
                this.remotingServer.Start();
            }

            if (this.remotingServerStatusViewTimer == null)
            {
                this.remotingServerStatusViewTimer = new System.Timers.Timer();
                this.remotingServerStatusViewTimer.Elapsed += new System.Timers.ElapsedEventHandler(remotingServerStatusViewTimer_Elapsed);

                this.remotingServerStatusViewTimer.Interval = remotingServerStatusInterval;
                this.remotingServerStatusViewTimer.Start();
            }
            
            if (this.socketServer == null)
            {
                this.socketServer = new WebSocketServerWrapper(this);
                if (!this.socketServer.StartSuperWebSocketByProgramming(
                    this.webSocketServerIP, this.webSocketPort, this.webSocketSecureServerIP, this.webSocketSecurePort, this.runSecureSocketServer, this.certificatePath))
                {
                    throw new Exception("Starting websocket server failed");
                }
            }
        }

        private void StopAll()
        {
            this.StopKinect();

            if (this.remotingServer != null)
            {
                this.remotingServer.Stop();
                this.remotingServer = null;
            }

            if (this.remotingServerStatusViewTimer != null)
            {
                this.remotingServerStatusViewTimer.Stop();
                this.remotingServerStatusViewTimer.Dispose();
                this.remotingServerStatusViewTimer = null;
            }

            if (this.socketServer != null)
            {
                this.socketServer.StopSuperWebSocket();
                this.socketServer = null;
            }
        }    

        private ConnectivityStatus GetConnectionStatus(out int connectedDevicesCount, out int connectedClientsCount)
        {
            connectedDevicesCount = 0;
            connectedClientsCount = 0;

            ConnectivityStatus connectivityStatus = ConnectivityStatus.BothDisconnected;

            try
            {
                bool kinectDeviceConnected = this.kinectManager.IsAnyKinectDeviceConnected(out connectedDevicesCount);
                bool serviceClientConnected = this.socketServer.IsAnyAuthenticatedClient(out connectedClientsCount);

                if (kinectDeviceConnected && serviceClientConnected) { connectivityStatus = ConnectivityStatus.BothConnected; }
                else if (kinectDeviceConnected) { connectivityStatus = ConnectivityStatus.OnlyDeviceConnected; }
                else if (serviceClientConnected) { connectivityStatus = ConnectivityStatus.OnlyServiceClientConnected; }
            }
            catch (Exception exp) { this.Log(exp, "Exception while checking connection status of kinect device and data users. {0}", exp.Message); }

            return connectivityStatus;
        }

        private bool AuthenticateUtilityUser(String loginName, String password)
        {
            UtilityUser utilityUser = ConfigurationHelper.GetUtilitiyUserByLoginName(loginName);
            if (utilityUser != null)
            {
                if (String.Compare(utilityUser.Password, password, false) == 0)
                {
                    return true;
                }
            }

            return false;
        }

        private bool IsRegisteredNotification(NotificationType notificationType)
        {
            List<UtilityUserAllowedNotification> registeredNotifications = ConfigurationHelper.GetAllUtilityUserNotifications();

            if (registeredNotifications != null)
            {
                foreach (UtilityUserAllowedNotification notification in registeredNotifications)
                {
                    if (notificationType == notification.NotificationTypeEnm)
                    {
                        return true;
                    }
                }
            }
             return false;
        }

        private void Log(String logFormat, params Object[] args) { this.Log(null, logFormat, args); }

        private void Log(Exception exp, String logFormat, params Object[] args)
        {
            try { Logger.Log(exp, "[Service] >> {0}", String.Format(logFormat, args)); }
            catch { }
        }
        #endregion

        #region INTERNAL METHDOS
        internal void StartKinect()
        {
            if (this.kinectManager != null) return;

            //Program.context.Send(p =>
            //{
            this.kinectManager = new KinectManager();
            this.kinectManager.FrameReceived += new EventHandler<FrameReceivedArgs>(kinectManager_FrameReceived);
            this.kinectManager.SkeletonFrameReceived += new EventHandler<SkeletonFrameReceivedArgs>(kinectManager_SkeletonFrameReceived);
            this.kinectManager.NoSkeletonFrame += new EventHandler<NoSkeletonFrameArgs>(kinectManager_NoSkeletonFrame);
            this.kinectManager.NotificationReceived += new EventHandler<NotificationReceivedArgs>(kinectManager_NotificationReceived);
            this.kinectManager.Start();
            //}, null);
        }

        internal void StopKinect()
        {
            if (this.kinectManager == null) return;

            this.kinectManager.Stop();
            this.kinectManager = null;
        }
        #endregion
    }
}